/* eslint-disable no-case-declarations */
import { get, flatten, isArray, has } from "lodash";
import {
  ICompletionItem,
  ICursorInfo,
  IGetFieldsByTableName,
  ISelectStatement,
  ISource,
  IStatements,
  IStatement,
  IToken,
} from "./types";

export async function getCursorInfo(
  rootStatement: IStatements,
  keyPath: string[]
) {
  if (!rootStatement) {
    return null;
  }
  const cursorValue: IToken = get(rootStatement, keyPath);
  const cursorKey = keyPath.slice().pop();
  const parentStatement = get(
    rootStatement,
    keyPath.slice(0, keyPath.length - 1)
  );
  if (!parentStatement) {
    return null;
  }

  return (await judgeStatement(parentStatement, async (typePlusVariant) => {
    switch (typePlusVariant) {
      case "identifier.charset":
        return {
          type: "charset",
          token: cursorValue,
        };
      case "identifier.env":
        return {
          type: "env",
          token: cursorValue,
        };
      case "identifier.tableName":
      case "identifier.table":
        return {
          type: "tableName",
          variant: cursorKey,
          token: cursorValue,
          tableInfo: parentStatement,
        };
      case "identifier.column":
        if (cursorKey === "name") {
          return {
            type: "tableField",
            token: cursorValue,
          };
        }
        return null;

      case "identifier.columnAfterGroup":
        return {
          type: "tableFieldAfterGroup",
          token: cursorValue,
          groupName: parentStatement.groupName.value,
        };
      case "identifier.udfName":
        return {
          type: "udfName",
          token: cursorValue,
        };
      case "identifier.columnType":
        return {
          type: "columnType",
          token: cursorValue,
        };
      case "function":
        return {
          type: "functionName",
          token: cursorValue,
        };
      default:
    }
  })) as ICursorInfo;
}

export function findNearestStatement(
  rootStatement: IStatements,
  keyPath: string[],
  callback?: (value?: any) => boolean
): ISelectStatement | null {
  if (!rootStatement) {
    return null;
  }

  if (keyPath.length === 0) {
    return null;
  }

  const value = get(rootStatement, keyPath);
  if (!value) {
    throw Error("Path not found from ast!");
  }

  if (!value.token && value.type === "statement") {
    if (callback) {
      if (callback(value) === true) {
        return value;
      }
    } else {
      return value;
    }
  }

  if (keyPath.length > 1) {
    return findNearestStatement(
      rootStatement,
      keyPath.slice(0, keyPath.length - 1),
      callback
    );
  }
  return null;
}

export async function getFieldsFromStatement(
  rootStatement: IStatements,
  cursorKeyPath: string[],
  getFieldsByTableName: IGetFieldsByTableName
) {
  const cursorInfo = await getCursorInfo(rootStatement, cursorKeyPath);
  const cursorRootStatement = findNearestStatement(
    rootStatement,
    cursorKeyPath
  );
  if (!cursorRootStatement) {
    return [];
  }
  if (cursorInfo.token.statement === "alertStatement") {
    cursorRootStatement.tableInfo =
      cursorRootStatement.tableInfo[cursorInfo.token.index];
  }
  switch (cursorRootStatement.variant) {
    // Select statement
    case "select":
      return getFieldsByFromClauses(
        cursorRootStatement,
        get(cursorRootStatement, "from.sources", []),
        cursorInfo,
        getFieldsByTableName
      );
    // Join statement
    // 字段是 source 表的（自带 + join 的表）
    case "join":
      const parentCursorKeyPath = cursorKeyPath.slice();
      parentCursorKeyPath.pop();

      const parentSelectStatement = findNearestStatement(
        rootStatement,
        parentCursorKeyPath,
        (eachStatement) => {
          return eachStatement.variant === "select";
        }
      );

      return getFieldsByFromClauses(
        parentSelectStatement,
        get(parentSelectStatement, "from.sources", []),
        cursorInfo,
        getFieldsByTableName
      );
    case "alert":
    case "delete":
    case "update":
    default:
      return getFieldsByTableName(
        cursorRootStatement.tableInfo,
        cursorInfo.token.value,
        cursorRootStatement
      );
  }
}

async function getFieldsByFromClauses(
  rootStatement: IStatement | null,
  fromStatements: IStatement[],
  cursorInfo: ICursorInfo | null,
  getFieldsByTableName: IGetFieldsByTableName
): Promise<(ICompletionItem | null)[]> {
  const fields = await Promise.all(
    fromStatements.map((fromStatement) => {
      return getFieldsByFromClause(
        rootStatement,
        fromStatement,
        cursorInfo,
        getFieldsByTableName
      );
    })
  );

  return flatten(fields).filter((item) => {
    return !!item;
  });
}

async function getFieldsByFromClause(
  rootStatement: IStatement | null,
  fromStatement: IStatement,
  cursorInfo: ICursorInfo | null,
  getFieldsByTableName: IGetFieldsByTableName
): Promise<ICompletionItem[] | null> {
  return judgeStatement(fromStatement, async (typePlusVariant) => {
    switch (typePlusVariant) {
      case "statement.tableSource":
        // ignore joins
        const tableSourceFields = await getFieldsByFromClause(
          rootStatement,
          (fromStatement as any).source,
          cursorInfo,
          getFieldsByTableName
        );
        const joinsFields = isArray((fromStatement as any).joins)
          ? ((
              await getFieldsByFromClauses(
                rootStatement,
                get(fromStatement, "joins", []),
                cursorInfo,
                getFieldsByTableName
              )
            ).filter((t) => t !== null) as ICompletionItem[])
          : [];
        return (tableSourceFields || []).concat(joinsFields);
      case "statement.join":
        return getFieldsByFromClause(
          rootStatement,
          (fromStatement as any).join,
          cursorInfo,
          getFieldsByTableName
        );
      case "identifier.table":
        const itFromStatement = fromStatement as ISource;

        let originFields: ICompletionItem[] = await getFieldsByTableName(
          itFromStatement.name,
          cursorInfo?.token?.value || null,
          rootStatement
        );
        const tableNames: string[] = get(
          itFromStatement,
          "name.tableNames",
          []
        );

        let groupPickerName: string | null = null;
        const tableNameAlias: string = get(itFromStatement, "alias.value");

        // 如果有 alias,直接作为 groupPickerName
        if (tableNameAlias) {
          groupPickerName = tableNameAlias;
        } else {
          // 实现的 tableNames 数量
          let existKeyCount = 0;
          tableNames.forEach((tableName) => {
            const eachTableName = get(
              itFromStatement,
              `name.${tableName}.value`
            );
            if (eachTableName) {
              // eslint-disable-next-line no-plusplus
              existKeyCount++;
              groupPickerName = eachTableName;
            }
          });

          // 如果 existKeyCount 大于 1，则不提供 groupPickerName
          if (existKeyCount > 1) {
            groupPickerName = null;
          }
        }

        originFields = originFields.map((originField) => {
          return {
            ...originField,
            tableInfo: itFromStatement.name,
            // 如果仅有一个 tableNames 有值，就用那个作为 groupPickerName，否则没有
            groupPickerName,
            // existKeyCount
            //     ? null
            //     : _.get(itFromStatement, 'alias.value') || _.get(itFromStatement, 'name.tableName.value') || null,
            originFieldName: originField.label,
          } as ICompletionItem;
        });
        return originFields;
      case "statement.select":
      case "statement.delete":
        const ssFromStatement = fromStatement as ISelectStatement;

        let statementSelectFields: ICompletionItem[] = [];

        const fields = await getFieldsByFromClauses(
          ssFromStatement,
          ssFromStatement.from.sources,
          cursorInfo,
          getFieldsByTableName
        );

        // If select *, return all fields
        if (
          ssFromStatement.result.length === 1 &&
          ssFromStatement.result[0].name.value === "*"
        ) {
          statementSelectFields = fields.slice() as ICompletionItem[];
        } else {
          statementSelectFields = fields
            .map((field) => {
              const selectedField = ssFromStatement.result.find((result) => {
                if (get(result.name, "token") === true) {
                  return result.name.value === field?.label;
                }

                // Consider ${group}.${field}
                if (
                  get(result.name, "type") === "identifier" &&
                  get(result.name, "variant") === "columnAfterGroup"
                ) {
                  return get(result.name, "name.value") === field?.label;
                }

                // Consider ${group}.*
                if (
                  get(result.name, "type") === "identifier" &&
                  get(result.name, "variant") === "groupAll"
                ) {
                  return (
                    get(result.name, "groupName.value") ===
                    field?.groupPickerName
                  );
                }

                return false;
              });
              if (!selectedField) {
                return null;
              }

              if (selectedField.alias) {
                return {
                  ...field,
                  label: selectedField.alias.value,
                };
              }
              return field;
            })
            .filter((field) => {
              return field !== null;
            })
            .slice() as ICompletionItem[];
        }

        // If has alias, change
        if (has(ssFromStatement, "alias.value")) {
          statementSelectFields = statementSelectFields.map(
            (statementSelectField) => {
              return {
                ...statementSelectField,
                groupPickerName: get(ssFromStatement, "alias.value"),
              };
            }
          );
        }

        return statementSelectFields;
      default:
        return null;
    }
  });
}

async function judgeStatement<T>(
  statement: IStatement,
  callback: (typePlusVariant?: string) => Promise<T>
): Promise<T | null> {
  if (!statement) {
    return null;
  }

  if (statement.variant) {
    return callback(`${statement.type}.${statement.variant}`);
  }
  return callback(statement.type);
}

export async function findFieldExtraInfo(
  rootStatement: IStatements,
  cursorInfo: ICursorInfo,
  getFieldsByTableName: IGetFieldsByTableName,
  fieldKeyPath: string[]
): Promise<ICompletionItem | null> {
  const fields = await getFieldsFromStatement(
    rootStatement,
    fieldKeyPath,
    getFieldsByTableName
  );
  const field = fields.find((eachField) => {
    return eachField?.label === cursorInfo.token.value;
  });

  if (!field) return null;

  return field;
}
